package com.ftqh.tutorial.security.oauth2.config;

import com.ftqh.tutorial.security.oauth2.provider.approval.Oauth2UserApprovalHandler;
import com.ftqh.tutorial.security.oauth2.provider.client.OAuth2ClientDetailsService;
import com.ftqh.tutorial.security.oauth2.provider.code.RedisAuthorizationCodeServices;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.*;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.util.Assert;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Created by Administrator on 2017/6/14.
 */
@Configuration
public class OAuth2ServerConfig {
    private static final String OAUTH2_RESOURCE_ID = "oauth2";
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        private AuthenticationManager authenticationManager;

        private OAuth2ClientDetailsService clientDetailsService;

        private RedisConnectionFactory redisConnectionFactory;


        AuthorizationServerConfiguration(@Qualifier("authenticationManagerBean")AuthenticationManager authenticationManager,
                                         OAuth2ClientDetailsService clientDetailsService,RedisConnectionFactory redisConnectionFactory){
            Assert.notNull(authenticationManager, "authenticationManager must not be null!");
            Assert.notNull(clientDetailsService, "clientDetailsService must not be null!");
            Assert.notNull(redisConnectionFactory, "redisConnectionFactory must not be null!");
            this.authenticationManager = authenticationManager;
            this.clientDetailsService = clientDetailsService;
            this.redisConnectionFactory = redisConnectionFactory;
        }
        /**
         * authorization_code will store in redis
         */
        @Bean
        public AuthorizationCodeServices authorizationCodeServices(){
            return new RedisAuthorizationCodeServices();
        }
        /**
         * token(accessToken, RefreshToken) will store in redis
         */
        @Bean
        public TokenStore tokenStore(){
            return new RedisTokenStore(redisConnectionFactory);
        }
        /**
         *  reference AuthorizationServerEndpointsConfigurer -> private approvalStore()
         * @return
         * @throws Exception
         */
        @Bean
        public ApprovalStore approvalStore() throws Exception {
            TokenApprovalStore store = new TokenApprovalStore();
            store.setTokenStore(tokenStore());
            return store;
        }
        @Bean
        @Lazy
        @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
        public Oauth2UserApprovalHandler userApprovalHandler() throws Exception {
            Oauth2UserApprovalHandler handler = new Oauth2UserApprovalHandler();
            handler.setApprovalStore(approvalStore());
            handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
            handler.setClientDetailsService(clientDetailsService);
            //add a property: useApprovalStore
            handler.setUseApprovalStore(true);
            return handler;
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    .tokenStore(tokenStore())
                    .approvalStore(approvalStore())
                    .userApprovalHandler(userApprovalHandler())
                    .authenticationManager(authenticationManager)
                    .authorizationCodeServices(authorizationCodeServices())
                    .addInterceptor(endpointsInterceptor());
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.withClientDetails(clientDetailsService);
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
            //defalut realm is "oauth2/client"
            //oauthServer.realm("oauth2/client");
            oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
        }

        private HandlerInterceptorAdapter endpointsInterceptor(){
            return new HandlerInterceptorAdapter() {
                @Override
                public void postHandle(HttpServletRequest request,
                                       HttpServletResponse response, Object handler,
                                       ModelAndView modelAndView) throws Exception {
                    if (modelAndView != null
                            && modelAndView.getView() instanceof RedirectView) {
                        RedirectView redirect = (RedirectView) modelAndView.getView();
                        String url = redirect.getUrl();
                        if (url.contains("code=") || url.contains("error=")) {
                            HttpSession session = request.getSession(false);
                            if (session != null) {
                                session.invalidate();
                            }
                        }
                    }
                }
            };
        }
    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(OAUTH2_RESOURCE_ID).stateless(false);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                // Since we want the protected resources to be accessible in the UI as well we need
                // session creation to be allowed (it's disabled by default in 2.0.6)
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .and()
                .requestMatchers()
                    .antMatchers("/oauth/users/**", "/oauth/clients/**","/me")
                    .and()
                .authorizeRequests()
                    .antMatchers("/me").access("#oauth2.hasScope('read')")
                    .regexMatchers(HttpMethod.DELETE, "/oauth/users/([^/].*?)/tokens/.*")
                        .access("#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('write')")
                    .regexMatchers(HttpMethod.GET, "/oauth/clients/([^/].*?)/users/.*")
                        .access("#oauth2.clientHasRole('ROLE_CLIENT') and (hasRole('ROLE_USER') or #oauth2.isClient()) and #oauth2.hasScope('read')")
                    .regexMatchers(HttpMethod.GET, "/oauth/clients/.*")
                        .access("#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('read')");
            // @formatter:on
        }
    }
}
